#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <ArduinoJson.h>
#include <TimeLib.h>
#include <WiFiUdp.h>

#define ENABLE_GxEPD2_GFX 0

#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <U8g2_for_Adafruit_GFX.h>
#include "simsun.h"

#include <ESPDateTime.h>
#include "my_datetime.h"
static const char ntpServerName[] = "ntp1.aliyun.com";
const int timeZone = 8;

WiFiUDP Udp;
unsigned int localPort = 8888;
time_t getNtpTime();
void sendNTPpacket(IPAddress &address);

String years;

// GxEPD2_BW<GxEPD2_213, GxEPD2_213::HEIGHT> display(GxEPD2_213(/*CS=D8*/ 15, /*DC=D3*/ 4, /*RST=D4*/ 2, /*BUSY=D2*/ 5));
//GxEPD2_BW<GxEPD2_213, GxEPD2_213::HEIGHT> display(GxEPD2_213(/*CS=D8*/ 2, /*DC=D3*/ 0, /*RST=D4*/ 4, /*BUSY=D2*/ 5));
// GxEPD2_BW<GxEPD2_420c, GxEPD2_420c::HEIGHT> display(GxEPD2_420c(/*CS=D8*/ SS, /*DC=D3*/ 4, /*RST=D4*/ 2, /*BUSY=D2*/ 5)); // GDEH0154D67
GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(/*CS=D8*/ 15, /*DC=D3*/ 4, /*RST=D4*/ 2, /*BUSY=D2*/ 5));
// GxEPD2_BW<GxEPD2_750c, GxEPD2_750c::HEIGHT> display(GxEPD2_750c(/*CS=D8*/ 15, /*DC=D3*/ 4, /*RST=D4*/ 2, /*BUSY=D2*/ 5));
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
BearSSL::WiFiClientSecure clientssl;
WiFiClient client;

// *********农历
String nl;

// *********一言
String yiyan;
// *********天气
String temperature;

void getHitokoto(); //一言
void EPD_init(); //初始化
void getNL(); //
void getWeather(); //天气
void startEPD(); //显示
void nowTime(); //当前时间

void connWIFI()
{
  // const char *startwifi = "正在连接无线网络...";
  // display.fillScreen(GxEPD_WHITE);
  // display.display();
  // display.setPartialWindow(0, display.height() / 2 - 20, display.width(), 32);
  // u8g2Fonts.setFont(simsun);
  // display.firstPage();
  // do
  // {
  //   u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(startwifi)) / 2, display.height() / 2, startwifi);
  // } while (display.nextPage());
  WiFi.mode(WIFI_STA);       //切换为STA模式
  WiFi.setAutoConnect(true); //设置自动连接
  WiFi.begin("ChaoFan", "8368C47148");
  Serial.println("");
  Serial.print("Connect WiFi");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println();
    Serial.println("WiFi Connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    display.firstPage();
    do
    {
      const char *connwifi = "无线网络已连接！";
      u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(connwifi)) / 2, display.height() / 2 - 7, connwifi);
      u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(WiFi.localIP().toString().c_str())) / 2, display.height() / 2 + 7, WiFi.localIP().toString().c_str());
    } while (display.nextPage());
  }
}

void EPD_init()
{
  // display.init(0, false);
  display.init();
  display.setRotation(3);//旋转
  u8g2Fonts.begin(display);
  u8g2Fonts.setFontMode(3);
  u8g2Fonts.setFontDirection(0);
  u8g2Fonts.setForegroundColor(GxEPD_BLACK);
  u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
}

void setup()
{
  Serial.begin(115200);
  EPD_init();
  connWIFI();

  setup_datetime();
  calc_month_calendar();

  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(3600 * 6);
  nowTime();
  getNL();
  getWeather();
  getHitokoto();
  display.refresh();
}

void formatTimeDigits(char strOut[3], int num)
{
  strOut[0] = '0' + (num / 10);
  strOut[1] = '0' + (num % 10);
  strOut[2] = '\0';
}


void nowTime()
{
  char strOut[3];
  formatTimeDigits(strOut, hour()); //格式化小时
  String nowHour = strOut;
  formatTimeDigits(strOut, minute()); //格式化分钟
  String nowMinute = strOut;
  formatTimeDigits(strOut, month()); //格式化月
  String fmonth = strOut;
  formatTimeDigits(strOut, day()); //格式化日
  String fday = strOut;
  String nowWeek;
  switch (weekday())
  {
  case 1:
    nowWeek = "星期日";
    break;
  case 2:
    nowWeek = "星期一";
    break;
  case 3:
    nowWeek = "星期二";
    break;
  case 4:
    nowWeek = "星期三";
    break;
  case 5:
    nowWeek = "星期四";
    break;
  case 6:
    nowWeek = "星期五";
    break;
  case 7:
    nowWeek = "星期六";
    break;
  }

  String nowTime = nowHour + ":" + nowMinute;
  String nowDate = String(year()) + "年" + fmonth + "月" + fday + "日" + " " + nowWeek;
  years = String(year()) + fmonth + fday;
  
  //第一页，实现农历、一言、时钟、天气功能
  display.firstPage();
  do
  {
    // display.setPartialWindow(0, 0, 200, 14);
    u8g2Fonts.setFont(simsun);
    u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(nowDate.c_str())) / 2, 40, nowDate.c_str());

    u8g2Fonts.setFont(u8g2_font_logisoso78_tn);//matrix
    u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(nowTime.c_str())) / 2, 130, nowTime.c_str());

    u8g2Fonts.setFont(simsun);
    u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(nl.c_str())) / 2, 20, nl.c_str());

    u8g2Fonts.setFont(simsun);
    u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(yiyan.c_str())) / 2, 285, yiyan.c_str());
    
    u8g2Fonts.setFont(weather);
    u8g2Fonts.drawUTF8(display.width() - u8g2Fonts.getUTF8Width(temperature.c_str()) - 2, 22, temperature.c_str());
    const char* g_WEEKDAY_EN[] = {"日", "一", "二", "三", "四", "五", "六"};
    int index = 0;
    int start_y = 180;
    for (index = 0; index < 7;index++){//日期头
      u8g2Fonts.setFont(simsun);
      u8g2Fonts.drawUTF8(10+50 + 10 + ((index + 6) % 7) * 40, 160, g_WEEKDAY_EN[index]);
    }
    int mday = g_month_week[0];
    int curday = DateTime.getParts().getMonthDay();
    
    #define CALENDAR_BLOCK_WIDTH 40
    #define CALENDAR_BLOCK_HEIGHT 30

    for (index = 1; index <= mday;index++){//计算每行日期
      String dStr(index);
      if(index<10)
      {
        dStr = " " + dStr;
      }
      if (index == curday)
      {
        display.writeLine(50 + 10 + 10 + ((g_month_week[index] + 6) % 7) * CALENDAR_BLOCK_WIDTH, start_y + 2,
                          50 +10 + 10 + ((g_month_week[index] + 6) % 7) * CALENDAR_BLOCK_WIDTH + (CALENDAR_BLOCK_WIDTH / 2),
                          start_y + 2, GxEPD_BLACK);
      }
      else
      {
        u8g2Fonts.setForegroundColor(GxEPD_BLACK);
      }
      u8g2Fonts.setFont(simsun);
      u8g2Fonts.drawUTF8(49 + 10 + 10 + ((g_month_week[index] + 6) % 7) * CALENDAR_BLOCK_WIDTH, start_y, dStr.c_str());
      if (g_month_week[index]<=0 && index!= mday){//间隔换行
        // start_y += CALENDAR_BLOCK_HEIGHT;
        start_y += 15;

      }
    }
   
  } while (display.nextPage());
  
}

void getHitokoto()
{
  Serial.println("HItokoto!");
  clientssl.setInsecure();
  HTTPClient http;
  http.setTimeout(5000);
  if (http.begin(clientssl, "https://v1.hitokoto.cn/?max_length=17&encode=text"))
  {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
    {
      String payload = http.getString();
      yiyan = payload;
      Serial.println(payload.c_str());
    }
    else
    {
      Serial.println(httpCode);
    }
  }
  else
  {
    Serial.printf("[HTTP} Unable to connect\n");
  }
  http.end();
  // display.firstPage();
  // do
  // {
    // display.setPartialWindow(0, 104, 250, 18);
  //   u8g2Fonts.setFont(simsun);
  //   u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(yiyan.c_str())) / 2, 118, yiyan.c_str());
  // } while (display.nextPage());
}

void getNL()
{
  Serial.println("NL!");
  HTTPClient http;
  http.setTimeout(5000);
  String URL = "http://tool.bitefu.net/jiari/?d=" + years + "&info=1";
  if (http.begin(client, URL))
  {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
    {
      String payload = http.getString();
      StaticJsonDocument<768> doc;
      DeserializationError error = deserializeJson(doc, payload);

      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      String yearname = doc["yearname"];
      String nonglicn = doc["nonglicn"];
      String shengxiao = doc["shengxiao"];
      nl = yearname + shengxiao + "年" + nonglicn;
    }
    else
    {
      Serial.println(httpCode);
    }
  }
  http.end();
  // display.firstPage();
  // do
  // {
  //   u8g2Fonts.setFont(simsun);
  //   u8g2Fonts.drawUTF8((display.width() - u8g2Fonts.getUTF8Width(nl.c_str())) / 2, 60, nl.c_str());
  // } while (display.nextPage());
}

void getWeather()
{
  Serial.println("Weather!");
  
  clientssl.setInsecure();
  HTTPClient http;
  http.setTimeout(5000);
  if (http.begin(clientssl, "https://api.seniverse.com/v3/weather/now.json?key=SVXaNRbIsYm3xBT4D&location=xuchang&language=zh-Hans&unit=c")) //自行在心知天气申请API
  {
    int httpCode = http.GET();
    Serial.println(httpCode);
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
    {
      String payload = http.getString();
      StaticJsonDocument<512> doc;
      DeserializationError error = deserializeJson(doc, payload);

      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      JsonObject results = doc["results"][0];
      JsonObject results_now = results["now"];
      String now_temperature = results_now["temperature"];
      temperature = now_temperature + "℃";
      Serial.println(temperature);
    }
    else
    {
      Serial.println(httpCode);
    }
  }
  http.end();
  // display.firstPage();
  // do
  // {
  //   display.setPartialWindow(200, 0, 50, 22);
  //   u8g2Fonts.setFont(weather);
  //   u8g2Fonts.drawUTF8(display.width() - u8g2Fonts.getUTF8Width(temperature.c_str()) - 2, 22, temperature.c_str());
  // } while (display.nextPage());
}

void startEPD()
{
  nowTime();

  if (minute() % 30 == 0)
  {
    getWeather();
  }
  if (hour() % 6 == 0 && minute() == 0)
  {
    getNL();
  }
  if (minute() % 10 == 0)
  {
    getHitokoto();
    display.refresh();
  }
  display.hibernate();
}

void loop()
{
  if (second() == 0)
  {
    EPD_init();
    startEPD();
  }
  delay(1000);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48;     // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0)
    ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500)
  {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE)
    {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 = (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011; // LI, Version, Mode
  packetBuffer[1] = 0;          // Stratum, or type of clock
  packetBuffer[2] = 6;          // Polling Interval
  packetBuffer[3] = 0xEC;       // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}